<?php
/**
* This file belongs to a webappkit package (kit)
* @link http://webappkit.net
* @package databeans
* @subpackage datetime
* @author J.Ducastel <nospam0@ducastel.name>
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*/

/**
* datetime instance wrapper.
* <p>supported formats :</p>
* <ul>
* <li>ISO 8601<li>
* <li>Mysql date</li>
* <li>Mysql datetime</li>
* <li>Mysql timestamp</li>
* <li>PHP timestamps</li>
* <li>PHP getdate() array</li>
* </ul>
* @uses duration
* @todo extend ISO8601 support
* @todo add time zones support
*/
class DateTimeBean extends DataBean {
/*------------------------------------------------------------------------------
                                                                         members
------------------------------------------------------------------------------*/
    /**
    * @var int year value
    */
    public $year=0;
    protected function _year_required() { return true; }
    protected function _year_inputfilter($x) { return intval($x); }
    protected function _year_check($x) { return is_int($x); }

    /**
    * @var int month number 1-12
    */
    public $mon=1;
    protected function _mon_required() { return true; }
    protected function _mon_inputfilter($x) { return intval($x); }
    protected function _mon_check($x) {
        return is_int($x) and $x>0 and $x<13;
    }

    /**
    * @var int month's day number 1-31
    */
    public $mday=1;
    protected function _mday_required() { return true; }
    protected function _mday_inputfilter($x) { return intval($x); }
    protected function _mday_check($x) {
        return is_int($x) and $x>0 and $x<=$this->daysInMonth();
    }/**/

    //function _yday_get() { return $this->yearDay(); }

    /**
    * @var int hours 0-23
    */
    public $hours=0;
    protected function _hours_required() { return true; }
    protected function _hours_inputfilter($x) { return intval($x); }
    protected function _hours_check($x) {
        return is_int($x) and $x>=0 and $x<25;
    }

    /**
    * @var int minutes 0-59
    */
    public $minutes=0;
    protected function _minutes_required() { return true; }
    protected function _minutes_inputfilter($x) { return intval($x); }
    protected function _minutes_check($x) {
        return is_int($x) and $x>=0 and $x<60;
    }

    /**
    * @var int seconds 0-59
    */
    public $seconds=0;
    protected function _seconds_required() { return true; }
    protected function _seconds_inputfilter($x) { return intval($x); }
    protected function _seconds_check($x) {
        return is_int($x) and $x>=0 and $x<60;
    }

    /**
    * @var string en|fr localization setting
    */
    public $locale='en';
    protected function _locale_check($x) {
        return in_array($x,array('en','fr'));
    }

/*------------------------------------------------------------------------------
                                                                   global config
------------------------------------------------------------------------------*/
    /**
    * @var bool enable/disable set() and loadData().
    */
    //var $_databean_lock_members=false;
    /**
    * @var bool enable/disable loadStructure() and lockStructure().
    */
    //var $_databean_lock_structure=true;

    /**
    * @var string default output format for toString()
    */
    protected $_datetimebean_tostring_default='%Y%m%dT%H:%i:%s'; // default toString format

/*------------------------------------------------------------------------------
                                                                     constructor
------------------------------------------------------------------------------*/
    /**
    * constructor alias.
    * @access public
    * @param mixed $input see class doc for supported formats.
    * if null, defaults to current datetime
    * @param string $format force input format
    */
    public function dateTimeBean($input=null,$format=null) {
        $this->__construct($input,$format);
    }/**/

    /**
    * "real" constructor.
    * @access public
    * @param mixed $input see class doc for supported formats.
    * if null, defaults to current datetime
    * @param string $format force input format
    */
    public function __construct($input=null,$format=null) {
        //$this->structureLock();
        if (is_null($input))
            $input=getdate();
        $this->_datetimebean_setfrominput($input,$format);
    }/**/
/*------------------------------------------------------------------------------
                                                                  public methods
------------------------------------------------------------------------------*/
    /**
    * getter/setter for year
    * @access public
    * @param int $new optional
    * @return mixed bool if $new provided, else current value
    */
    public function year($new=null) {
        if (!is_null($new))
            return $this->set('year',$new);
        return $this->get('year');
    }/**/

    /**
    * getter/setter for mon
    * @access public
    * @param int $new optional
    * @return mixed bool if $new provided, else current value
    */
    public function mon($new=null) {
        if (!is_null($new))
            return $this->set('mon',$new);
        return $this->get('mon');
    }/**/

    /**
    * getter/setter for mday
    * @access public
    * @param int $new optional
    * @return mixed bool if $new provided, else current value
    */
    public function mday($new=null) {
        if (!is_null($new))
            return $this->set('mday',$new);
        return $this->get('mday');
    }/**/

    /**
    * getter/setter for hours
    * @access public
    * @param int $new optional
    * @return mixed bool if $new provided, else current value
    */
    public function hours($new=null) {
        if (!is_null($new))
            return $this->set('hours',$new);
        return $this->get('hours');
    }/**/

    /**
    * getter/setter for mon
    * @access public
    * @param int $new optional
    * @return mixed bool if $new provided, else current value
    */
    public function minutes($new=null) {
        if (!is_null($new))
            return $this->set('minutes',$new);
        return $this->get('minutes');
    }/**/

    /**
    * getter/setter for minutes
    * @access public
    * @param int $new optional
    * @return mixed bool if $new provided, else current value
    */
    public function seconds($new=null) {
        if (!is_null($new))
            return $this->set('seconds',$new);
        return $this->get('seconds');
    }/**/

    /**
    * return month name with current locale.
    * @access public
    * @return string
    */
    public function monthName() {
        switch ($this->locale) {
            case 'fr':
                $months=array('','Janvier','Fevrier','Mars','Avril','Mai','Juin','Juillet','Aout','Septembre','Octobre','Novembre','Decembre');
                break;
            case 'en':
            default:
                $months=array('','January','February','March','April','May','June','July','August','September','October','November','December');
        }
        return $months[$this->mon];
    }/**/

    /**
    * return week day's name with current locale.
    * @access public
    * @return string
    */
    public function dayName() {
        switch ($this->locale) {
            case 'fr':
                $days=array('Dimanche','Lundi','Mardi','Mercredi','Jeudi','Vendredi','Samedi');
                break;
            case 'en':
            default:
                $days=array('Sunday','Monday','Tuesday','Wednesday','Tursday','Friday','Saturday');
        }
        return $days[$this->weekDay()];
    }/**/

    /**
    * sets all values to 0
    * @access public
    * @return bool
    * /
    function reset() {
        $this->year=0;
        $this->mon=1;
        $this->mday=1;
        $this->hours=0;
        $this->minutes=0;
        $this->seconds=0;
        return true;
    }/**/

    /**
    * return instance for current datetime
    * @access public
    * @static
    * @return datetimebean
    */
    static function now() {
        return new datetimebean(getdate(),'php_getdate');
    }/**/

    /**
    * sets from various input such as string, getdate array, timestamp
    * @access public
    * @param mixed $input
    * @param string $format input format, wil guessed if null
    * @return bool
    */
    public function setFromInput($input,$format=null) {
        return $this->_dateTimeBean_setFromInput($input,$format);
    }

    /**
    * auto-corrects invalid dates, ie values within invalid ranges
    * if some value is outside range, it will fix it and reports it to sub-value
    * @access public
    * @return bool
    */
    public function autoCorrect() {
        // seconds correction
        if (!$this->_correctSeconds())
            return false;
        // minutes correction
        if (!$this->_correctMinutes())
            return false;
        // hours correction
        if (!$this->_correctHours())
            return false;
        // mday correction
        if (!$this->_correctMday())
            return false;
        // month correction
        if (!$this->_correctMon())
            return false;
        return true;
    }/**/

    /**
    * Return date as string, upon a format as in mysql date_format.
    * <ul>
    * <li>%% => %</li>
    * <li>%a weekday name 3 letters</li>
    * <li>%b month name, 3 letters</li>
    * <li>%d, %e month day, 01..31</li>
    * <li>%H, %k hours, 00..23</li>
    * <li>%h, %I, %l hours, 00..12</li>
    * <li>%i minutes, 00..60</li>
    * <li>%j, year day, 001..366</li>
    * <li>%m month, 01..12</li>
    * <li>%M month name</li>
    * <li>%p AM ou PM</li>
    * <li>%s, %S seconds 00..59</li>
    * <li>%U week number, starting on sunday  00..53</li>
    * <li>%u week number, starting on monday  00..53</li>
    * <li>%W week day name</li>
    * <li>%w week day number starting on sunday, 0..6 </li>
    * <li>%y year on 2 digits</li>
    * <li>%Y year on 4 digits</li>
    * </ul>
    * @param string $format as in mysql DATE_FORMAT default %Y%m%dT%H:%i:%s (iso8601)
    * @access public
    * @return string
    * @todo : D,j,l,N,s,w,z,W,h etc
    */
    public function toString($format=null) {
        if (!is_string($format))
            $format=$this->_datetimebean_tostring_default;
        $format=str_replace('%Y',sprintf('%04d',$this->year),$format);
        $format=str_replace('%y',substr(strval($this->year),2,2),$format);
        $format=str_replace('%m',sprintf('%02d',$this->mon),$format);
        $format=str_replace('%d',sprintf('%02d',$this->mday),$format);
        $format=str_replace('%H',sprintf('%02d',$this->hours),$format);
        $format=str_replace('%i',sprintf('%02d',$this->minutes),$format);
        $format=str_replace('%s',sprintf('%02d',$this->seconds),$format);
        $format=str_replace('%j',sprintf('%03d',$this->yearDay()),$format);
        $format=str_replace('%w',sprintf('%01d',$this->weekDay()),$format);
        $format=str_replace('%M',$this->monthName(),$format);
        $format=str_replace('%W',$this->dayName(),$format);
        return $format;
    }/**/

    /**
    * to mysql date format = %Y-%m-%d
    * @access public
    * @return string
    */
    public function toMysqlDate() {
        return $this->toString('%Y-%m-%d');
    }/**/

    /**
    * to mysql datetime format = %Y-%m-%d %H:%i:%s
    * @access public
    * @return string
    */
    public function toMysqlDateTime() {
        return $this->toString('%Y-%m-%d %H:%i:%s');
    }/**/

    /**
    * to ISO8601 format = %Y-%m-%dT%H:%i:%s.
    * @link http://www.cl.cam.ac.uk/~mgk25/iso-time.html
    * @access public
    * @return string
    */
    public function toIso8601() {
        return $this->toString('%Y-%m-%dT%H:%i:%s');
    }/**/

    /**
    * to ISO8601 format = %Y-%m-%dT%H:%i:%s.
    * @link http://www.cl.cam.ac.uk/~mgk25/iso-time.html
    * @access public
    * @return string
    */
    public function toRfc3339() {
        return $this->toString('%Y-%m-%dT%H:%i:%sZ');
    }/**/

    /**
    * for xml-rpc usage %Y%m%dT%H:%i:%s
    * @access public
    * @return string
    */
    public function toXmlRpc() {
        return $this->toString('%Y%m%dT%H:%i:%s');
    }/**/

    /**
    * formated as french date %d/%m/%Y
    * @access public
    * @return string
    */
    public function toFrDate() {
        return $this->toString('%d/%m/%Y');
    }/**/

    /**
    * return as php timestamp.
    * @access public
    * @return int
    */
    public function toTimestamp() {
        return mktime(
            $this->hours
            , $this->minutes
            , $this->seconds
            , $this->mon
            , $this->mday
            , $this->year);
    }/**/

    /**
    * return as days count since date 0000-00-00.
    * invalid for dates before day 0
    * @access public
    * @return int
    */
    public function toDays() {
        $days=0;
        // years days
        $days+=$this->year*365.25;
        // past days in year
        $days+=$this->yearDay();
        return $days;
    }/**/

    /**
    * return as seconds count since date 0000-00-00 00:00:00.
    * invalid for dates before second 0
    * @access public
    * @return int
    */
    public function toSeconds() {
        $seconds=0;
        // years seconds
        $seconds+=$this->year*31556926;
        // past days in year seconds
        $seconds+=($this->yearDay()-1)*86400;
        // past hours in current day seconds
        $seconds+=$this->hours*3600;
        // past minutes in current hours seconds
        $seconds+=$this->minutes*60;
        // current seconds
        $seconds+=$this->seconds;
        return $seconds;
    }/**/

    /**
    * add some time to the datetimebean.
    * accepts integers as seconds, or {duration} instance
    * @see duration
    * @param int $input seconds, or duration instance
    * @access public
    * @return bool
    */
    public function add($input) {
        // converting input to seconds
        if (is_a($input,'datetimebean')) {
            $input=$input->toSeconds();
        } else if (is_a($input,'duration')) {
            $input=$input->getAsSeconds();
        } else if (!is_integer($input)) {
            trigger_error('dateTimeBean->add(): incorrect input format');
            return false;
        }
        // adding seconds
        $this->addSeconds($input);
        return $this->_correctSeconds();
    }/**/

    /**
    * adds some years to current date
    * @access public
    * @param int $val years to add
    * @return bool
    */
    public function addYears($val) {
        if (!is_integer($val)) {
            return false;
        }
        $this->year+=$val;
        return true;
    }/**/

    /**
    * adds some days to current date
    * @access public
    * @param int $val days to add
    * @return bool
    */
    public function addDays($val) {
        if (!is_integer($val)) {
            trigger_error('dateTimeBean->addDays(): must be integer');
            return false;
        }
        // storing current mday for backup
        $old=$this->mday;
        // going wild
        $this->mday+=$val;
        // correcting invalid dates
        if ($this->autocorrect())
            return true;
        // back to old value
        $this->mday=$old;
        return false;
    }/**/

    /**
    * adds some years to current date
    * @access public
    * @param int $val seconds to add
    * @return bool
    */
    public function addSeconds($val) {
        if (!is_integer($val)) {
            trigger_error('dateTimeBean->addSeconds(): must be integer');
            return false;
        }
        // storing current mday for backup
        $old=$this->seconds;
        // going wild
        $this->seconds+=$val;
        // echo wakapi::vardump($this);
        // correcting invalid dates
        if ($this->_correctSeconds())
            return true;
        // back to old value
        $this->seconds=$old;
        return false;
    }/**/

    /**
    * soustracts some time to the date.
    * First convert input into another datetimebean instance, then soustracts its time to the date
    * @access public
    * @param mixed $date
    * @return bool
    * /
    function soustract($input,$format=NULL) {
        if (!is_a($input,'datetimebean')) {
            // if integer, avoid considering timestamp
            if (is_int($input) and is_null($format))
                $format='seconds';
            // building new instance
            $input=&new datetimebean($input,$format);
        }
        // cascade soustraction
        if (($this->seconds-=$input->seconds)<0) {
            $this->seconds+=60; $this->minutes-=1;
        }
        if (($this->minutes-=$input->minutes)<0) {
            $this->minutes+=60; $this->hours-=1;
        }
        if (($this->hours-=$input->hours)<0) {
            $this->hours+=24; $this->mday-=1;
        }
        if (($this->mday-=$input->mday)<1) {
            $this->mon-=1; $this->mday+=$this->daysInMonth();
        }
        if (($this->mon-=$input->mon)<1) {
            $this->mon+=12; $this->year-=1;
        }
        $this->year-=$input->year;
        return true;
    }/**/

    /**
    * computes days count since/before another date.
    * positive if comparing to a date before itself
    * @param mixed $other datetimebean|string|int date to compare to
    * return int
    */
    public function daysDistance($other) {
        if (!is_a($other,'datetimebean'))
            $other=new datetimebean($other);
        // echo '<p>'.$this->tomysqldate().' daysDistance with '.$other->tomysqldate().'</p>';
        // $dist=0;
        // comparing to past or future ?
        if ($this->isAfter($other)) {
            // comparing to past
            // same year ?
            if ($this->year==$other->year()) {
                // just soustracting other yearDay to self yearDay
                $dist=$this->yearDay()-$other->yearDay();
            } else {
                // days between other date and end of its year
                $dist=1+$other->daysInYear()-$other->yearDay(); //echo "<p>$dist</p>";
                // adding full years between other date and self
                for ($year=$other->year()+1; $year<$this->year(); $year++) {
                    $dist+=dateTimeBean::daysInYear($year);
                }
                // adding days since start of self year
                $dist+=$this->yearDay();
            }
        } else {
            // comparing to future, this will give a negative count
            // same year ?
            if ($this->year==$other->year()) {
                // just soustracting other yearDay to self yearDay
                $dist=$this->yearDay()-$other->yearDay();
            } else {
                // days until end of year
                $dist=1+$this->daysInYear()-$this->yearDay();
                // adding full years between self and other date
                for ($year=$this->year()+1; $year<$other->year(); $year++) {
                    $dist+=dateTimeBean::daysInYear($year);
                }
                // adding other date yearday
                $dist+=$other->yearDay();
            }
        }
        return $dist;
    }/**/

    /**
    * returns days count in month.
    * may be used static
    * @access public
    * @param int year if null, bean's year is taken
    * @param int mon if null, bean's mon is taken
    * @return int
    */
    public function daysInMonth($year=null,$mon=null) {
        if (is_null($year) and $this)
            $year=$this->year;
        if (is_null($mon) and $this)
            $mon=$this->mon;
        if ($mon==2) {
            if ($year%4) return 28;
            elseif ($year%100) return 29;
            elseif ($year%1000) return 28;
            else return 29;
        } elseif ($mon==4 or $mon==6 or $mon==9 or $mon==11) return 30;
        return 31;
    }/**/

    /**
    * return days count in year.
    * may be used static
    * @access public
    * @param int $year optional, use own year if not provided
    * @return int
    */
    public function daysInYear($year=null) {
        if (is_null($year) and isset($this)) {
            $year=$this->year;
        }
        return 337 + datetimebean::daysInMonth($year,2);
    }/**/

    /**
    * returns year's day = days count from year start
    * @access public
    * @return int
    */
    public function yearDay() {
        $year=$this->year();
        $mon=$this->mon();
        $yday=0;
        // days for past months
        for ($n=1; $n<$mon; $n++) {
            $yday+=$this->daysInMonth($year,$n);
        }
        // days since month start
        $yday+=$this->mday;
        return $yday;
    }/**/

    /**
    * return week day, starting on sunday=0.
    * use Mike Keith algorithm
    * @access public
    * @return int
    */
    public function weekDay() {
        $year=$this->year;
        $mon=$this->mon;
        $mday=$this->mday;
        //
        $z=($mon>=3)?$year:$year-1;
        $D=floor((23*$mon)/9);
        $D+=$mday;
        $D+=4;
        $D+=$year;
        $D+=floor($z/4);
        $D-=floor($z/100);
        $D+=floor($z/400);
        if ($mon>=3)
            $D-=2;
        $wday=$D%7;
        return intval($wday);
    }/**/

    /**
    * return week number, ISO 8601
    * STILL ONWORK
    * @link http://fr.wikipedia.org/wiki/ISO_8601
    * @access public
    * @return int 1-53
    */
    public function weekNumber() {
        // Calculer le jeudi de la même semaine que la date cherchée. On calculera le numéro de semaine de ce jeudi pour être sûr de compter dans la bonne année sans test particulier. (Ce jeudi peut tomber l'année précédente ou suivante, c'est tout l'intérêt.)
        $weekThursday = new datetimebean($this);
        switch ($weekThursday->weekDay()) {
            case 1:
                $weekThursday->addDays(3);
                break;
            case 2:
                $weekThursday->addDays(2);
                break;
            case 3:
                $weekThursday->addDays(1);
                break;
            case 5:
                $weekThursday->addDays(-1);
                break;
            case 6:
                $weekThursday->addDays(-2);
                break;
            case 0:
                $weekThursday->addDays(-3);
                break;
        }
        // echo $weekThursday->toMysqlDate();
        // On considère le 4 janvier de la même année que ce jeudi
        $january4=new datetimebean();
        $january4->set('year',$weekThursday->get('year'));
        $january4->set('mon',1);
        $january4->set('mday',4); //echo '<p>jan 4 : '.$january4->tomysqldate().'</p>';
        // On calcule le lundi de la même semaine que ce 4 janvier (identifiant ainsi le début du comptage; noter que ce lundi peut lui même tomber l'année encore avant: peu importe)
        $startMonday = new datetimebean($january4);
        switch ($january4->weekDay()) {
            case 2:
                $startMonday->addDays(-1);
                break;
            case 3:
                $startMonday->addDays(-2);
                break;
            case 4:
                $startMonday->addDays(-3);
                break;
            case 5:
                $startMonday->addDays(-4);
                break;
            case 6:
                $startMonday->addDays(-5);
                break;
            case 0:
                $startMonday->addDays(-6);
                break;
        }
        //echo '<p>startMonday : '.$startMonday->tomysqldate().'</p>';
        // On calcule le nombre de jours écoulés entre nos deux dates particulières (ce jeudi et ce lundi).
        //echo '<p>weekThursday : '.$weekThursday->toMysqlDate().'</p>';
        $days=$weekThursday->daysDistance($startMonday);
        //echo "<p>$days</p>";
        // On divise par 7 (arrondir à l'entier supérieur). On a le résultat voulu.
        return ceil($days/7);
    }/**/

    /**
    * tells if date is in the past
    * @access public
    * @return bool
    */
    public function isPast() {
        return $this->isBefore(datetimebean::now());
    }/**/

    /**
    * tells if the date is in the future
    * @access public
    * @return bool
    */
    public function isFuture() {
        return $this->isAfter(datetimebean::now());
    }/**/

    /**
    * tells if current datetime is before another datetime
    * @access public
    * @param mixed $input converted to datetimebean if necessary
    * @param string $format date format, will be guessed if necessary
    * @return bool
    */
    public function isBefore($input,$format=null) {
        // converting to datetimebean if required
        if (!is_a($input,'datetimebean'))
            $input=new datetimebean($input,$format);
        // echo '<p>is '.$this->toIso8601().' before '.$input->toiso8601().' ?</p>';
        foreach (array('year','mon','mday','hours','minutes','seconds') as $part) {
            if ($this->$part<$input->$part)
                // no need to continue, is before
                return true;
            if ($this->$part!=$input->$part)
                // no need to continue, is not before
                return false;
            // maybe, continue
        }
        return false;
    }/**/

    /**
    * tells if current datetime is after another datetime
    * @access public
    * @param mixed $input converted to datetimebean if necessary
    * @param string $format date format, will be guessed if necessary
    * @return bool
    */
    public function isAfter($input,$format=null) {
        // converting to datetimebean if required
        if (!is_a($input,'datetimebean'))
            $input=new datetimebean($input,$format);
        foreach (array('year','mon','mday','hours','minutes','seconds') as $part) {
            if ($this->$part>$input->$part)
                // no need to continue, is before
                return true;
            if ($this->$part!=$input->$part)
                // no need to continue, is not before
                return false;
            // maybe, continue
        }
        return false;
    }/**/

    /**
    * sets self as random date from base within range
    * @param mixed $base datetimebean, or date format. default now
    * @param int range in seconds optional, default 10 years
    * @access public
    * @return bool
    */
    public function setRandom($base=null,$range=null) {
        // setting as base
        if (!is_null($base))
            $this->_datetimebean_setFromInput($base);
        // setting range
        if (!is_numeric($range)) {
            // 10 years
            $range=10*365.25*24*3600;
        }
        $rand=rand(0,intval($range)); //echo $rand;
        $ok=$this->add($rand);
        //echo wakaffvar($this).wakaffvar($base).wakaffvar($range);
        return $ok;
    }/**/

    /**
    * sets locale
    * @param string $locale en|fr
    * @access public
    * @return bool
    */
    public function setLocale($locale) {
        return $this->set('locale',$locale);
    }/**/
/*------------------------------------------------------------------------------
                                                                 members methods
------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------
                                                                 private methods
------------------------------------------------------------------------------*/
    /**
    * sets from input.
    * input format will be guessed if not provided.
    * searches for method _datetimebean_setfrom_{format}
    * @param mixed $input
    * @param string $format optional
    * @access protected
    * @return
    */
    protected final function _datetimebean_setFromInput($input,$format=null) {
        // guessing format if necessary
        if (is_null($format))
            $format=$this->_dateTimeBean_guessFormat($input);
        // echo "<p>setting from $format, $input</p>";
        // searching appropriate method
        $method='_datetimebean_setfrom_'.$format;
        if (!$format or !method_exists($this,$method)) {
            trigger_error('datetimebean: couldn\'t set from input');
            return false;
        }
        return $this->$method($input);
    }/**/

    /**
    * tries to define an input date's format
    * @access protected
    * @param mixed &$input
    * @return string format : php_timestamp|php_getdate|mysql_date|mysql_datetime|mysql_timestamp
    */
    protected final function _dateTimeBean_guessFormat(& $input) {
        switch (true) {
            // is already a datetimebean
            case is_a($input,'datetimebean'):
                return 'datetimebean';
                break;
            // php/unix timestamp
            case is_integer($input) and $input>0 and $input<2147483647:
                return 'php_timestamp';
                break;
            // php getdate
            case is_array($input) and isset($input['year']) and isset($input['mon']) and isset($input['mday'])
            and isset($input['hours']) and isset($input['seconds']) and isset($input['minutes']):
                return 'php_getdate';
                break;
            // partial php getdate
            case is_array($input) and isset($input['year']) and isset($input['mon']) and isset($input['mday']):
                return 'php_getdate';
                break;
            // ISO 8601
            case ereg('^[0-9]{8}T(([0-1][0-9])|(2[0-3]))(:[0-5][0-9]){2}Z?$',$input):
                return 'iso8601';
            // mysql date
            case ereg ('^[0-9]{4}-((0[1-9])|(1[0-2]))-((0[1-9])|([1-2][0-9])|(3[0-1]))$', $input):
                return 'mysql_date';
                break;
            // mysql datetime
            case ereg ('^[0-9]{4}-((0[1-9])|(1[0-2]))-((0[1-9])|([1-2][0-9])|(3[0-1])) '
            .'(([0-1][0-9])|(2[0-3]))(:[0-5][0-9]){2}$', $input):
                return 'mysql_datetime';
                break;
            // mysql timestamp
            case ereg ('^[0-9]{14}$', $input):
                return 'mysql_timestamp';
                break;
            // mysql date
            // ce qui suit permet des dates incorrectes type 0000-00-00
            case ereg ('^[0-9]{4}-((0[0-9])|(1[0-2]))-((0[0-9])|([1-2][0-9])|(3[0-1]))$', $input):
                return 'mysql_date';
                break;
            case ereg ('^[0-9]{4}-((0[0-9])|(1[0-2]))-((0[0-9])|([1-2][0-9])|(3[0-1])) '
            .'(([0-1][0-9])|(2[0-3]))(:[0-5][0-9]){2}$', $input):
                return 'mysql_datetime';
                break;
            // "french" usual date dd/mm/yy or dd/mm/yyyy
            case ereg('^((0?[0-9])|([1-2][0-9])|(3[0-1]))/((0?[0-9])|(1[0-2]))/[0-9]{2,4}$', $input):
                return 'fr_date';
                break;
            default:
                trigger_error('date: unkown date format for '.$input.'('.gettype($input).')',E_USER_NOTICE);
                return false;
                break;
        }
    }
    /**
    * sets date from another datetimebean (copy)
    * @param datetimebean $bean
    * @return bool
    */
    protected final function _dateTimeBean_setFrom_datetimebean($bean) {
        if (!is_a($bean,'datetimebean'))
            return false;
        $this->year = $bean->year();
        $this->mon = $bean->mon();
        $this->mday = $bean->mday();
        $this->hours = $bean->hours();
        $this->minutes = $bean->minutes();
        $this->seconds = $bean->seconds();
        return true;
    }
    /**
    * sets date from php timestamp format
    * @param int $time
    * @access private
    * @return bool
    */
    protected final function _dateTimeBean_setfrom_php_timestamp($time) {
        $date=getdate($time);
        return $this->_dateTimeBean_setfrom_php_getdate($date);
    }/**/

    /**
    * sets date from php getdate array
    * accepts partial arrays (date only)
    * @param array $date
    * @access private
    * @return bool
    */
    protected final function _dateTimeBean_setfrom_php_getdate($date) {
        $this->set('year',$date['year']);
        $this->set('mon',$date['mon']);
        $this->set('mday',$date['mday']);
        $this->set('hours',$date['hours']?$date['hours']:0);
        $this->set('minutes',$date['minutes']?$date['minutes']:0);
        $this->set('seconds',$date['seconds']?$date['seconds']:0);
        return true;
        //return $this->loadMembers($date);
    }/**/

    /**
    * sets date from mysql date format
    * @param string $date
    * @access private
    * @return bool
    */
    protected final function _dateTimeBean_setfrom_mysql_date($date) {
        $date=explode('-',$date);
        if (count($date)!=3) {
            trigger_error("dateTimeBean: incorrect mysql_date format : $date",E_USER_NOTICE);
            return false;
        }
        $this->setmember('mday',$date[2]);
        $this->setmember('mon',$date[1]);
        $this->setmember('year',$date[0]);
        $this->setmember('hours',0);
        $this->setmember('minutes',0);
        $this->setmember('seconds',0);
        return true;
    }/**/

    /**
    * sets date form mysql datetime format
    * @param string $datetime
    * @access private
    * @return bool
    */
    protected final function _dateTimeBean_setfrom_mysql_datetime($datetime) {
        if (strlen($datetime)!=19) return false;
        $this->seconds=intval(substr($datetime,17,2));
        $this->minutes=intval(substr($datetime,14,2));
        $this->hours=intval(substr($datetime,11,2));
        $this->mday=intval(substr($datetime,8,2));
        $this->mon=intval(substr($datetime,5,2));
        $this->year=intval(substr($datetime,0,4));
        return true;
    }/**/

    /**
    * sets date form mysql timestamp format
    * @param string $timestamp
    * @access private
    * @return bool
    */
    protected final function _dateTimeBean_setfrom_mysql_timestamp($timestamp) {
        $mysql_timestamp=strval($timestamp); // echo '<p>'.$mysql_timestamp.'<p>';
        if (!strlen($mysql_timestamp)==14) return false;
        $this->year=intval(substr($mysql_timestamp,0,4));
        $this->mon=intval(substr($mysql_timestamp,4,2));
        $this->mday=intval(substr($mysql_timestamp,6,2));
        $this->hours=intval(substr($mysql_timestamp,8,2));
        $this->minutes=intval(substr($mysql_timestamp,10,2));
        $this->seconds=intval(substr($mysql_timestamp,12,2));
        return true;
    }/**/

    /**
    * sets from french date %d/%m/(%y|%Y)
    * @param string $fr_date
    * @access private
    * @return bool
    */
    protected final function _dateTimeBean_setfrom_fr_date($fr_date) {
        $date=explode("/",$fr_date);
        if (!count($date)==3) {
            trigger_error("datetimebean: format fr_date incorrect : $strdate",E_USER_NOTICE);
            return false;
        }
        //$this->seconds=0;
        //$this->minutes=0;
        //$this->hours=0;
        $this->mday=intval($date[0]);
        //$this->wday=0;
        $this->mon=intval($date[1]);
        $this->year=intval($date[2]);
        //$this->yday=0;
        //$this->weekday='';
        //$this->month' => $this->mois[intval($date[1])]);
        return true;
    }/**/

    /**
    * sets from iso8601
    * @link http://fr.wikipedia.org/wiki/ISO_8601
    * @todo accept/parse partial, shortened versions
    * @param string $iso8601
    * @access private
    * @return bool
    */
    protected final function _dateTimeBean_setFrom_iso8601($iso8601) {
        if (!is_string($iso8601) /*or strlen($iso8601)!=17*/)
            return false;
        $this->setMember('year',substr($iso8601,0,4));
        $this->setMember('mon',substr($iso8601,4,2));
        $this->setMember('mday',substr($iso8601,6,2));
        $this->setMember('hours',substr($iso8601,9,2));
        $this->setMember('minutes',substr($iso8601,12,2));
        $this->setMember('seconds',substr($iso8601,15,2));
        return true;
    }/**/

    /**
    * sets datetimebean from seconds
    * seconds in year : 31557600
    * @param int $seconds
    * @access private
    * @return bool
    * /
    function _dateTimeBean_setFrom_seconds($seconds) {
        //echo "setting from seconds $seconds";
        $this->reset();
        while ($seconds>31556925) {
            $this->year++;
            $seconds-=31556926;
        }
        while ($seconds>=$this->daysInMonth()*24*3600) {
            $this->mon++;
            $seconds-=$this->daysInMonth($this->year,$this->mon+1)*24*3600;
        }
        while ($seconds>86399) {
            $this->mday++;
            $seconds-=86400;
        }
        while ($seconds>3599) {
            $this->hours++;
            $seconds-=3600;
        }
        while ($seconds>59) {
            $this->minutes++;
            $seconds-=60;
        }

        $this->seconds=$seconds;
        //echo wakdebug::vardump($this);
        return true;
    }/**/

    /**
    * corrects seconds if out of range
    * @access private
    * @return bool
    */
    protected final function _correctSeconds() {
        if (!is_integer($this->seconds))
            return false;
        if ($this->seconds>60) {
            $this->minutes+=intval(floor($this->seconds/60));
            $this->seconds=$this->seconds%60;
        } else if ($this->seconds<0) {
            $this->minutes+=intval(floor($this->seconds/60));
            $this->seconds=$this->seconds%60;
            if ($this->seconds<0)
                $this->seconds+=60;
        }
        // echo wakapi::vardump($this);
        return $this->_correctMinutes();
    }/**/

    /**
    * corrects minutes if out of range
    * @access private
    * @return bool
    */
    protected final function _correctMinutes() {
        if (!is_integer($this->minutes))
            return false;
        if ($this->minutes>60) {
            $this->hours+=intval(floor($this->minutes/60));
            $this->minutes=$this->minutes%60;
        } else if ($this->minutes<0) {
            $this->hours+=intval(floor($this->minutes/60));
            $this->minutes=$this->minutes%60;
            if ($this->minutes<0)
                $this->minutes+=60;
        }
        // echo wakapi::vardump($this);
        return $this->_correctHours();
    }/**/

    /**
    * corrects hours if out of range
    * @access private
    * @return bool
    */
    protected final function _correctHours() {
        if (!is_integer($this->hours))
            return false;
        if ($this->hours>23) {
            $this->mday+=intval(floor($this->hours/24));
            $this->hours=$this->hours%24;
        } else if ($this->hours<0) {
            $this->mday+=intval(floor($this->hours/24));
            $this->hours=$this->hours%24;
            if ($this->hours<0)
                $this->hours+=24;
        }
        return $this->_correctMday();
    }/**/

    /**
    * corrects mday
    * @access private
    * @return bool
    */
    protected final function _correctMday() {
        if (!is_integer($this->mday))
            return false;
        while ($this->mday>$this->daysInMonth()) {
            $this->mday-=$this->daysInMonth();
            $this->mon++;
            // correcting new mon if required
            $this->_correctMon();
        }
        while ($this->mday<1) {
            $this->mon--;
            $this->mday+=$this->daysInMonth();
            // correcting new mon if required
            $this->_correctMon();
        }
        return true;
    }/**/

    /**
    * corrects mon
    * @access private
    * @return bool
    */
    protected final function _correctMon() {
        if (!is_integer($this->mon))
            return false;
        if ($this->mon>12) {
            $this->year+=intval(floor($this->mon/12));
            $this->mon=$this->mon%12;
        } else if ($this->mon<1) {
            /*$this->year-=intval(floor($this->mon/12));
            $this->mon=$this->mon%12;*/
            while ($this->mon<1) {
                $this->mon+=12;
                $this->year--;
            }
        }
        return true;
    }/**/
}
?>